package com.cloud.utils;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@Component
@Slf4j
@Data
public class HandwritingRedisLockUtils {
    private Thread locaThread;

    private static final Long SUCCESS = 1L;
    //key  是redis的key  value是uuid
    public static volatile Map<String, String> redisKeyMap = new HashMap();
    //key  是redis的value value是看门狗线程
    public static volatile Map<String, Thread> threadMap = new HashMap();
    @Resource
    public RedisTemplate<String, Object> redisTemplate;

    //redis的key 过期时间
    public Boolean tryLock(String key, int time, boolean flag) {
        return this.tryLock(key, time, flag, 3, 1000);
    }

    //redis的key 过期时间
    public Boolean tryLock(String key, int time) {
        return this.tryLock(key, time, false, 3, 1000);
    }

    /**
     * 加锁
     *
     * @param key               lock key
     * @param time              超时时间，单位秒
     * @param flag              是否开启看门狗
     * @param retryTimes        重试次数
     * @param tryIntervalMillis 重试间隔，毫秒
     * @return
     */
    public Boolean tryLock(String key, int time, boolean flag, int retryTimes, int tryIntervalMillis) {
        String value = UUID.randomUUID().toString().replace("-", "");
        log.info("线程" + Thread.currentThread().getName() + "，value=" + value);
        String script = "if redis.call('setNx',KEYS[1],ARGV[1])==1  then if redis.call('get',KEYS[1])==ARGV[1] then  return redis.call('expire',KEYS[1],ARGV[2])  else return 0 end else return 0 end";
        RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        for (int i = 0; i < retryTimes; i++) {
            Long execute = redisTemplate.execute(redisScript, Collections.singletonList(key), value, time + "");
            boolean result = SUCCESS.equals(execute);
            if (result) {
                log.info("获取到锁的线程：" + Thread.currentThread().getName() + " , " + "value = " + value);
                //ture  开启看门狗
                if (flag) {
                    this.watchDog(key, time, value);
                }
                locaThread = Thread.currentThread();
                redisKeyMap.put(key, value);

                return result;
            } else {
                //尝试几次则放弃
                log.info("线程" + Thread.currentThread().getName() + "占用锁失败，自旋等待结果, value = " + value);
                try {
                    Thread.sleep(tryIntervalMillis);
                } catch (InterruptedException e) {
                    continue;
                }
            }
        }
        return false;
    }

    //存在bug，可能看门狗在10秒内创建了一千个线程，应改为删除锁就要释放看门狗
    //可以在new Thread  获取线程，之后保存如map，key是 分布式锁的value，value是线程
    //释放锁就会调用map里对应的线程stop
    void watchDog(String key, int time, String value) {
        new Thread(() -> {
            //
            threadMap.put(value, Thread.currentThread());
            log.info("看门狗线程开启：" + Thread.currentThread().getName() + " , " + "value = " + value);
            //是否结束的阈值
            boolean flag = true;
            while (flag) {
                //取到的key等于当初线程的key则，继续延时，否则关闭续约
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('expire', KEYS[1],ARGV[2]) else return 0 end";
                RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
                Long execute = redisTemplate.execute(redisScript, Collections.singletonList(key), value, time + "");
                //false
                if (!SUCCESS.equals(execute)) {
                    flag = false;
                    log.info("线程" + Thread.currentThread().getName() + "退出续约");
                }
                try {
                    //线程休眠10秒再去续约
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    //redis的key 过期时间
    public Boolean releaseLock(String key) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        if (locaThread == Thread.currentThread()) {
            Long result = redisTemplate.execute(redisScript, Collections.singletonList(key), redisKeyMap.get(key));
            if (SUCCESS.equals(result)) {
                log.info("线程:" + Thread.currentThread().getName() + "，释放锁成功" + "value = " + redisKeyMap.get(key));
                //清除看门狗线程
                if (threadMap.get(redisKeyMap.get(key)) != null) {
                    threadMap.get(redisKeyMap.get(key)).stop();
                    log.info("看门狗线程结束:" + threadMap.get(redisKeyMap.get(key)).getName() + "，释放锁成功" + "value = " + redisKeyMap.get(key));
                    threadMap.remove(redisKeyMap.get(key));
                }
            }
            return SUCCESS.equals(result);
        }
        return false;
    }
}
